//
// Created by chao.li on 2023/9/11.
//

#ifndef SRC_FRACTAL_H
#define SRC_FRACTAL_H

#include <vector>
#include "drawer_basic.h"
#include "image.h"

class fractal_rec : public drawer_basic
{
public:
    void draw_spiral(const vec2& start, double length, double scale, double radian = 0){
        draw_line(start, length, radian);
        auto next_length = length * scale;
        if(next_length >= 1){
            auto dir = get_direction(radian);
            draw_spiral(start + dir * length, next_length, scale,radian + PI / 4);
        }
    }

    void draw_koch(const vec2& start, double length, double radian = 0){
        if(length <= 1){
            draw_line(start, length, radian);
        } else{
            auto pl = length / 3;
            draw_koch(start, pl, radian);

            auto dir = get_direction(radian);
            vec2 next = start + dir * pl;
            radian += PI / 3;
            draw_koch(next, pl, radian);

            dir =  get_direction(radian);
            next = next + dir * pl;
            radian -= PI * 2 / 3;
            draw_koch(next, pl, radian);

            dir =  get_direction(radian);
            next = next + dir * pl;
            radian += PI / 3;
            draw_koch(next, pl, radian);
        }
    }
};

class fractal_unit
    :public drawer_basic
{
public:
    static double get_unit_length(const std::vector<double>& gene){
        auto end = vec2(0,0);
        double radian = 0;
        for (int i = 0; i <gene.size(); ++i) {
            radian += gene[i];
            end = end + get_direction(radian);
        }
        return end.length();
    }

    static double get_unit_length(const std::vector<std::pair<double, double>>& gene){
        auto end = vec2(0,0);
        double radian = 0;
        for (int i = 0; i <gene.size(); ++i) {
            radian += gene[i].first;
            auto length = std::fabs(gene[i].second);
            end = end + get_direction(radian)*length;
        }
        return end.length();
    }

    static void angle2radian(std::vector<double>& gene){
        for (int i = 0; i <gene.size(); ++i) {
            gene[i] = gene[i] * drawer_basic::PI / 180;
        }
    }

    static void angle2radian(std::vector<std::pair<double, double>>& gene){
        for (int i = 0; i <gene.size(); ++i) {
            gene[i].first = gene[i].first * drawer_basic::PI / 180;
        }
    }
};

class fractal_gene
    : public drawer_basic
{
public:
    void draw_gene(const vec2& start, double length, const std::vector<double>& gene, double unit_scale,
                   double dir = 0, int depth = -1)
    {
        if(length <= 1){
            draw_point(start);
            return ;
        }

        double unit_length = length * unit_scale;
        auto p = start;
        for (int i = 0; i < gene.size(); ++i) {
            dir += gene[i];
            if(gene[i] != -PI){
                if(depth == 0){
                    draw_line(p, unit_length, dir);
                } else{
                    draw_gene(p, unit_length, gene, unit_scale, dir, depth-1);
                }
            }
            p = p + get_direction(dir) * unit_length;
        }
    }

    void draw_gene(const vec2& start, double length,
                   const std::vector<std::pair<double, double>>& gene, double unit_scale,
                   double dir = 0, int depth = -1, double min_step = 1)
    {
        if(length <= min_step){
            if(length <= 1){
                draw_point(start);
            } else{
                draw_line(start, length, dir);
            }
            return ;
        }

        auto p = start;
        double unit_length = 0;
        for (int i = 0; i < gene.size(); ++i) {
            unit_length = length * unit_scale * std::fabs(gene[i].second);
            dir += gene[i].first;
            auto next = p + get_direction(dir) * unit_length;
            if(gene[i].first != -PI){
                if(depth == 0){
                    draw_line(p, unit_length, dir);
                } else{
                    if(gene[i].second < 0){
                        draw_gene(next, unit_length, gene, unit_scale, PI + dir, depth-1, min_step);
                    } else {
                        draw_gene(p, unit_length, gene, unit_scale, dir, depth-1, min_step);
                    }
                }
            }
            p = next;
        }
    }

    double dim2scale(int N, double dim)
    {
        return std::exp(-log(N) / dim);
    }
};

class fractal_fix
    : public drawer_basic
{
public:
    void draw_fix(const vec2& start, const vec2& end,
                  double radian, double scale, double min_step = 1, bool flip1 = false, bool flip2 = false)
    {
        auto line = vec2(end-start);
        auto length = line.length();
        if(length <= min_step){
            draw_point(start);
            return;
        }
        auto dir = std::atan2(line.y, line.x);
        auto next_dir = get_direction(dir + radian);
        auto mid =start + next_dir * length * scale;
        draw_point(mid);
        if(flip1){
            draw_fix(mid, start, radian, scale, min_step, flip1, flip2);
        } else{
            draw_fix(start, mid, radian, scale, min_step, flip1, flip2);
        }
        if(flip2){
            draw_fix(end, mid, radian, scale, min_step, flip1, flip2);
        } else{
            draw_fix(mid, end, radian, scale, min_step, flip1, flip2);
        }
    }
};

class fractal_map
        : public drawer_basic
{
public:
    void draw_map(const vec2& p, int n_br, int n_st, double s_st, double r_st, double length,
                  double scale, double min_step = 1, int depth = -1){
        if(length <= min_step ||depth == 0){
//            std::cout << "x: " << x << " y: " << y << " length: " << length << " depth: " << depth << std::endl;
            draw_point(p);
            return ;
        }
        double alpha = PI * 2 / n_br;
        for (int i = 0; i < n_br; ++i) {
            auto next = p;
            double radian = PI / 2 + alpha * i;
            double len = length * s_st;
            for (int j = 0; j < n_st; ++j) {
                next = next + get_direction(radian) * len;
                draw_map(next, n_br, n_st, s_st, r_st, length * scale, scale, min_step, depth - 1);
                radian += r_st;
                len = len * s_st;
            }
        }
        draw_map(p, n_br, n_st, s_st, r_st, length * scale, scale, min_step, depth - 1);
    }
};
#endif //SRC_FRACTAL_H
